package edu.ynu.se.xiecheng.achitectureclass.filter;

import edu.ynu.se.xiecheng.achitectureclass.util.JwtToken;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JWTFilter extends BasicHttpAuthenticationFilter {
    /**
     * Token检查
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //如果携带Token，说明要进行验证
        if(isLoginAttempt(request,response)){
            try{
                //进入 executeLogin 方法执行登入，检查 token 是否正确
                executeLogin(request,response);
                //若无异常，则说明Token有效，放行。接下来继续去验证权限
                return true;
            }catch (Exception e){
                //若有异常，则说明登录的Token无效，直接跳转报错信息
                //注意 ： 登录验证Token相关的异常都会被抛到这里。但是授权相关的异常会抛到系统里，所以需要一个全局异常处理类处理授权异常
                responseError(response,e.getMessage());
                return false;
            }
        }
        //如果请求头不存在 Token，则可能是执行登陆操作或者是游客状态访问，无需检查 token，直接返回 true。
        // 剩下的交给授权注解来检查权限 ：
        //      - 若不需要权限，则可以直接访问
        //      - 若需要权限，则这里没登录授权操作会抛出异常，有全局异常类处理
        return true;
    }

    //防止isAccessAllowed返回false，执行父类的onAccessDenied里的login，重复请求
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return false;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        // String token = httpServletRequest.getHeader("Token");
        
        String authHeader = httpServletRequest.getHeader("Authorization");
        // 去掉 "Bearer " 前缀，获取实际的 token
        String token = authHeader.substring(7);  // "Bearer ".length() == 7
        JwtToken jwtToken = new JwtToken(token);
        // 提交给realm进行登入，如果错误他会抛出异常并被捕获
        getSubject(request, response).login(jwtToken);
        // 如果没有抛出异常则代表登入成功，返回true
        return true;
    }

    /**
     * 判断用户是否想要登入：检测 header 里面是否包含 Token 字段
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest)request;
        // String token = httpServletRequest.getHeader("Token");
        // return token!=null;
        String authHeader = httpServletRequest.getHeader("Authorization");
        // 检查是否有 Authorization 头，且是否以 "Bearer " 开头
        return authHeader != null && authHeader.startsWith("Bearer ");
    }

    /**
     * 将非法请求转到 /unauthorized/** 处理
     */
    private void responseError(ServletResponse response, String message) {
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            //设置编码，否则中文字符在重定向时会变为空字符串
            //**此处 中文乱码/空串 没有解决，标记一下**
            //message = URLEncoder.encode(message, "UTF-8");
            message = "Token&LoginError";
            httpServletResponse.sendRedirect("/unauthorized/" + message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

}
